/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2019年1月7日
 */
package com.massyframework.beanskin.guice.beancontext;

import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextAttributeEvent;
import javax.servlet.ServletContextAttributeListener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionIdListener;
import javax.servlet.http.HttpSessionListener;

import com.google.inject.Injector;
import com.massyframework.beanskin.assembly.Assembly;
import com.massyframework.beanskin.assembly.beans.BeansException;
import com.massyframework.beanskin.assembly.handling.EnvironmentHandler;
import com.massyframework.beanskin.assembly.handling.ReadyingException;
import com.massyframework.beanskin.assembly.handling.ReadyingHandler;
import com.massyframework.beanskin.initparam.InitParameters;
import com.massyframework.beanskin.service.ServicePropertiesBuilder;
import com.massyframework.beanskin.service.ServiceRegistration;
import com.massyframework.beanskin.service.ServiceRegistry;
import com.massyframework.beanskin.service.ServiceRepository;
import com.massyframework.beanskin.web.listener.DelegateListener;
import com.massyframework.beanskin.web.utils.ServletListenerUtils;


/**
 * 提供支持ServletContxtListener的Guice容器
 * @author  Huangkaihui
 *
 */
public final class GuiceServletContextListenerHandler extends GuiceHandler
        implements EnvironmentHandler, ReadyingHandler {

    private final String listenerName;
    private volatile boolean runAtJ2EE = true;
    private volatile ServiceRegistration<ServletContextListener> listenerRegistration;
    private volatile ServletContext servletContext;
    private volatile ServletContextListener listener;

    public GuiceServletContextListenerHandler() {
        this.listenerName = UUID.randomUUID().toString();
    }

    /**
     * 初始化
     *
     * @throws Exception 发生非预期的例外
     */
    @Override
    protected void init() throws Exception {
        super.init();
        final Assembly assembly = this.getAssociatedAssembly();
        ServiceRepository repository = this.getServiceRepository(assembly);

        ServletContext context = repository.findService(ServletContext.class).orElse(null);
        this.runAtJ2EE = context != null;
        if (this.runAtJ2EE){
            DelegateListener delegetListener =
                    new DelegateListener(assembly.getGroupId(), assembly.getModuleId(), assembly.getVersion(), this.listenerName);
            context.addListener(delegetListener);

            //初始化参数
            InitParameters params = ServletListenerUtils.getInitParameters(assembly.getConfig());
            List<String> names = params.getParameterNames();
            for (String name : names) {
                context.setAttribute(name, params.getParameter(name));
            }
        };
    }

    /**
     * 析构，释放资源
     */
    @Override
    public void destroy() {
        super.destroy();
    }

    /**
     * 执行就绪
     *
     * @throws ReadyingException 就绪时发生例外
     */
    @Override
    public void readying() throws ReadyingException {
        if (!this.runAtJ2EE) return;
        this.exportService();
    }

    /**
     * 退出就绪
     *
     * @throws ReadyingException 退出就绪时发生的例外
     */
    @Override
    public void unreadyed() throws ReadyingException {
        // 依赖服务未就绪
        this.unexportService();
    }

    /**
     * 在Web场景下注册的组件是否已准备就绪？<br>
     * Servlet的实例化要视注册时的选项，如果是首次请求时才实例化，那在发起首次请求前，本方法只能返回<code>false</code>
     *
     * @return {@link boolean},返回<code>true</code>表示已就绪，否则返回<code>false</code>
     */
    @Override
    public boolean isReady() {
        if (!this.runAtJ2EE) return false;
        return this.servletContext != null;
    }

    /**
     * 创建{@link GuiceBeanContextAdapter}
     *
     * @param injector {@link Injector}
     * @return {@link GuiceBeanContextAdapter}
     */
    @Override
    protected GuiceBeanContextAdapter createBeanContext(Injector injector) throws Exception {
        GuiceBeanContextAdapter result =  super.createBeanContext(injector);

        Map<String, ServletContextListener> listenerMap = result.getBeansOfType(ServletContextListener.class);
        if (listenerMap.size() == 0){
            throw new BeansException("cannot found ServletContextListener from guice.");
        }
        if (listenerMap.size() > 1) {
            throw new BeansException("found more than one ServletContextListener from guice.");
        }

        ServletContextListener listener = listenerMap.values().iterator().next();
        listener.contextInitialized(new ServletContextEvent(this.servletContext));
        this.listener = listener;
        return result;
    }

    /**
     * 取消装配件上下文注册
     */
    @Override
    protected void unregisterAssemblyContext() {
        super.unregisterAssemblyContext();
        this.listener.contextDestroyed(new ServletContextEvent(this.servletContext));
        this.listener = null;
    }

    /**
     * 初始化ServletContext
     * @param sce {@link ServletContextEvent}
     */
    protected void initServletContext(ServletContextEvent sce) {
        this.servletContext = sce.getServletContext();
        try {
            this.getLifecycleManager().start();
        }catch(Exception e) {
            this.logError(e.getMessage(), e);
        }
    }

    /**
     * 释放ServletContext
     * @param sce {@link ServletContextEvent}
     */
    protected void destortedServletContext(ServletContextEvent sce) {
        this.servletContext = null;
    }

    /**
     * 输出服务，服务输出后，DelegateListener才能和输出的服务挂接上
     */
    protected synchronized void exportService() {
        if (this.listenerRegistration == null) {
            //注册服务
            Assembly assembly = this.getAssociatedAssembly();
            ServiceRepository repository = this.getServiceRepository(assembly);
            ServiceRegistry registry = repository.getServiceRegistry();

            Bridge bridge = new Bridge();
            Map<String, Object> props = ServicePropertiesBuilder.newInstance()
                    .name(this.listenerName)
                    .build();

            this.listenerRegistration =
                    registry.register(ServletContextListener.class, bridge, props);
        }

    }

    /**
     * 撤销服务输出
     */
    protected synchronized void unexportService(){
        //注销
        if (this.listenerRegistration != null){
            this.listenerRegistration.unregister();
            this.listenerRegistration = null;
        }
    }

    private class Bridge implements ServletContextListener,
            ServletContextAttributeListener,
            HttpSessionActivationListener,
            HttpSessionAttributeListener,
            HttpSessionBindingListener,
            HttpSessionIdListener,
            HttpSessionListener{

        @Override
        public void contextInitialized(ServletContextEvent sce) {
            initServletContext(sce);
        }

        @Override
        public void contextDestroyed(ServletContextEvent sce) {
            destortedServletContext(sce);
        }

        /**
         * Receives notification that an attribute has been added to the
         * ServletContext.
         *
         * @param event the ServletContextAttributeEvent containing the
         *              ServletContext to which the attribute was added, along with the
         *              attribute name and value
         */
        @Override
        public void attributeAdded(ServletContextAttributeEvent event) {
            ServletContextAttributeListener listener = this.asInterface(ServletContextAttributeListener.class);
            if (listener != null){
                listener.attributeAdded(event);
            }
        }

        /**
         * Receives notification that an attribute has been removed
         * from the ServletContext.
         *
         * @param event the ServletContextAttributeEvent containing the
         *              ServletContext from which the attribute was removed, along with
         *              the attribute name and value
         */
        @Override
        public void attributeRemoved(ServletContextAttributeEvent event) {
            ServletContextAttributeListener listener = this.asInterface(ServletContextAttributeListener.class);
            if (listener != null){
                listener.attributeRemoved(event);
            }
        }

        @Override
        public void attributeReplaced(ServletContextAttributeEvent event) {
            ServletContextAttributeListener listener = this.asInterface(ServletContextAttributeListener.class);
            if (listener != null){
                listener.attributeReplaced(event);
            }
        }

        /**
         * Notification that the session is about to be passivated.
         *
         * @param se
         */
        @Override
        public void sessionWillPassivate(HttpSessionEvent se) {
            HttpSessionActivationListener listener =
                    this.asInterface(HttpSessionActivationListener.class);
            if (listener != null){
                listener.sessionWillPassivate(se);
            }
        }

        /**
         * Notification that the session has just been activated.
         *
         * @param se
         */
        @Override
        public void sessionDidActivate(HttpSessionEvent se) {
            HttpSessionActivationListener listener =
                    this.asInterface(HttpSessionActivationListener.class);
            if (listener != null){
                listener.sessionDidActivate(se);
            }
        }

        /**
         * Receives notification that an attribute has been added to a
         * session.
         *
         * @param event the HttpSessionBindingEvent containing the session
         *              and the name and value of the attribute that was added
         */
        @Override
        public void attributeAdded(HttpSessionBindingEvent event) {
            HttpSessionAttributeListener listener =
                    this.asInterface(HttpSessionAttributeListener.class);
            if (listener != null){
                listener.attributeAdded(event);
            }
        }

        /**
         * Receives notification that an attribute has been removed from a
         * session.
         *
         * @param event the HttpSessionBindingEvent containing the session
         *              and the name and value of the attribute that was removed
         */
        @Override
        public void attributeRemoved(HttpSessionBindingEvent event) {
            HttpSessionAttributeListener listener =
                    this.asInterface(HttpSessionAttributeListener.class);
            if (listener != null){
                listener.attributeRemoved(event);
            }
        }

        /**
         * Receives notification that an attribute has been replaced in a
         * session.
         *
         * @param event the HttpSessionBindingEvent containing the session
         *              and the name and (old) value of the attribute that was replaced
         */
        @Override
        public void attributeReplaced(HttpSessionBindingEvent event) {
            HttpSessionAttributeListener listener =
                    this.asInterface(HttpSessionAttributeListener.class);
            if (listener != null){
                listener.attributeReplaced(event);
            }
        }

        /**
         * Notifies the object that it is being bound to
         * a session and identifies the session.
         *
         * @param event the event that identifies the
         *              session
         * @see #valueUnbound
         */
        @Override
        public void valueBound(HttpSessionBindingEvent event) {
            HttpSessionBindingListener listener =
                    this.asInterface(HttpSessionBindingListener.class);
            if (listener != null){
                listener.valueBound(event);
            }
        }

        /**
         * Notifies the object that it is being unbound
         * from a session and identifies the session.
         *
         * @param event the event that identifies
         *              the session
         * @see #valueBound
         */
        @Override
        public void valueUnbound(HttpSessionBindingEvent event) {
            HttpSessionBindingListener listener =
                    this.asInterface(HttpSessionBindingListener.class);
            if (listener != null){
                listener.valueUnbound(event);
            }
        }

        /**
         * Receives notification that session id has been changed in a
         * session.
         *
         * @param event        the HttpSessionBindingEvent containing the session
         *                     and the name and (old) value of the attribute that was replaced
         * @param oldSessionId the old session id
         */
        @Override
        public void sessionIdChanged(HttpSessionEvent event, String oldSessionId) {
            HttpSessionIdListener listener =
                    this.asInterface(HttpSessionIdListener.class);
            if (listener != null){
                listener.sessionIdChanged(event, oldSessionId);
            }
        }

        /**
         * Receives notification that a session has been created.
         *
         * @param se the HttpSessionEvent containing the session
         */
        @Override
        public void sessionCreated(HttpSessionEvent se) {
            HttpSessionListener listener =
                    this.asInterface(HttpSessionListener.class);
            if (listener != null){
                listener.sessionCreated(se);
            }
        }

        /**
         * Receives notification that a session is about to be invalidated.
         *
         * @param se the HttpSessionEvent containing the session
         */
        @Override
        public void sessionDestroyed(HttpSessionEvent se) {
            HttpSessionListener listener =
                    this.asInterface(HttpSessionListener.class);
            if (listener != null){
                listener.sessionDestroyed(se);
            }
        }

        /**
         * 转换为指定类型
         * @param requiredType
         * @param <T>
         * @return
         */
        protected <T> T asInterface(Class<T> requiredType){
            ServletContextListener listener = GuiceServletContextListenerHandler.this.listener;
            if (listener == null) return null;
            if (requiredType.isAssignableFrom(listener.getClass())){
                return requiredType.cast(listener);
            }

            return null;
        }
    }
}
